package br.edu.unifei.mestrado.mn;

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.mestrado.commons.graph.GraphWrapper;
import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.graph.RelationshipWrapper;
import br.edu.unifei.mestrado.commons.mn.GN;
import br.edu.unifei.mestrado.commons.partition.NullPartition;

public class CoarseHelper {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * Usado para fazer commit a cada 100 nós novos
	 */
	private static final int COMMIT_INTERVAL = 100;

	private Matching mathcing = null;

	/**
	 * Faz a contração do grafo gn(level) salvando os novos vertices e arestas em newGraph.
	 * 
	 * @param newLevel
	 * @param gn
	 * @param newGraph
	 * @return retorna true se houve diminuição de nós, senão retorna false.
	 */
	public boolean coarseOneLevel(final int newLevel, GN gn, GraphWrapper newGraph) {
		logger.warn("Inicio do coarse NIVEL: " + newLevel);

		// instancia o matching novamente para não usar o coarsed vertex do nivel anterior
		mathcing = new Matching();

		long delta = System.currentTimeMillis();

		GraphWrapper oldGraph = gn.getGraph(newLevel - 1);
		// coarseGraphRandom(gn.getGraph(level), newGrafo);
		coarseGraphLessNodeWeitht(oldGraph, newGraph);
		delta = System.currentTimeMillis() - delta;
		newGraph.printGraph();

		gn.setGraph(newGraph, new NullPartition(newLevel), newLevel);
		logger.warn("Qtd de vertices do nivel: " + newLevel + " : " + gn.getGraph(newLevel).getSizeNodes()
				+ " tempo gasto: " + delta + " ms");

		// UTIL: indica se diminuiu a quantidade de vertices
		return oldGraph.getSizeNodes() > newGraph.getSizeNodes();
	}

	/*
	 * Faz a contração usando emparelhamento ordenado pelo menor peso primeiro.
	 */
	protected void coarseGraphLessNodeWeitht(GraphWrapper oldGrafo, GraphWrapper newGrafo) {

		// UTIL: Esse treeMap é usado para saber se o nó já foi ou não locked.
		Map<NodeWrapper, Boolean> nodes = new TreeMap<NodeWrapper, Boolean>(new Comparator<NodeWrapper>() {
			@Override
			public int compare(NodeWrapper o1, NodeWrapper o2) {
				int diff = o1.getWeight() - o2.getWeight();
				if (diff == 0) {
					return (int) (o1.getId() - o2.getId());
				}
				return diff;
			}
		});

		// UTIL: esse loop já ordena e faz unlock dos nós.
		for (NodeWrapper node : oldGrafo.getAllNodes()) {
			nodes.put(node, false);
		}

		try {
			newGrafo.beginTransaction();
			int i = coarseGraphWithNodes(newGrafo, nodes);
			logger.info("Qtd vertices: " + oldGrafo.getSizeNodes() + " qtd Coarsed seq: " + i);
			newGrafo.endTransaction();
		} catch (Throwable e) {
			logger.error("Erro contraindo grafo nivel: " + newGrafo.getLevel(), e);
		} finally {
			newGrafo.finish();
		}
		setInsideOfForGraph(oldGrafo);
	}

	private int coarseGraphWithNodes(GraphWrapper newGrafo, Map<NodeWrapper, Boolean> nodes) {
		int i = 0;
		for (NodeWrapper node : nodes.keySet()) {
			if (!nodes.get(node)) { // --->>> !node.isLocked()
				for (RelationshipWrapper aresta : node.getRelationships()) {

					NodeWrapper v1 = aresta.getStartNode();
					if (!nodes.get(v1)) {
						NodeWrapper v2 = aresta.getEndNode();
						if (!nodes.get(v2)) {
							// se ambos vertices não foram usados
							// se a aresta tem as duas pontas no mesmo vertice
							if (v1.getId() == v2.getId()) {
								logger.error("1 - Verificar pq gera aresta pra ele mesmo. id: " + v1.getId());
								// TODO: Verificar se realmente deve descartar a aresta para ele mesmo.
								// UTIL: SIM!!! pode descartar
								continue;
							}
							mathcing.coarseVertex(newGrafo, v1, v2);

							// v1.lock(); v2.lock();
							nodes.put(v1, true);
							nodes.put(v2, true);
							i++;
							break; // break do for de dento, que é de arestas
						}
					}
				}
			}
			//UTIL: Esse if é para não subir muito a memória e o GC não ocupar muito CPU
			if (i % COMMIT_INTERVAL == 0) {
				newGrafo.endTransaction();
				newGrafo.finish();
				logger.info("Qtd de nós contraídos: " + i);
				newGrafo.beginTransaction();
			}
		}
		mathcing.processRemainingVertices(nodes, newGrafo);
		return i;
	}

	private void setInsideOfForGraph(GraphWrapper oldGrafo) {
		try {
			oldGrafo.beginTransaction();
			Map<NodeWrapper, Long> insideOf = mathcing.getInsideOf();
			for (NodeWrapper node : oldGrafo.getAllNodes()) {
				Long insideId = insideOf.get(node);
				if (insideId != null) {
					node.setInsideOf(insideId);
				}
			}
			oldGrafo.endTransaction();
		} catch (Throwable e) {
			logger.error("Erro salvando insideOf.", e);
		} finally {
			oldGrafo.finish();
		}
	}

//	/*
//	 * Faz a contração usando emparelhamento sequencial da lista de arestas
//	 */
//	private void coarseGraphSequence(GraphWrapper grafo, GraphWrapper newGrafo) {
//
//		int i = 0;
//		int count = 0;
//		// int levelGen = pow(10, level + 3);
//		// loop geral em todas as arestas
//		for (RelationshipWrapper aresta : grafo.getAllEdges()) {
//
//			NodeWrapper v1 = aresta.getStartNode();
//			if (!v1.isLocked()) {
//				NodeWrapper v2 = aresta.getEndNode();
//				if (!v2.isLocked()) {
//					// se ambos vertices não foram usados
//					if (v1.getId() == v2.getId()) {
//						logger.error("2 - Verificar pq gera aresta pra ele mesmo. id: "
//									+ v1.getId());
//						// TODO: Verificar se realmente deve descartar a aresta para si mesmo.
//						continue;
//					}
//					mathcing.coarseVertex(newGrafo, v1, v2);
//
//					v1.lock();
//					v2.lock();
//					i++;
//				}
//				count++;
//			}
//		}
//		logger.warn("Qtd vertices: " + count + " qtd Coarsed seq: " + i);
//		mathcing.processRemainingVertices(grafo.getAllNodes(), newGrafo);
//	}
//
//	/**
//	 * Faz a contração usando emparelhamento aleatorio
//	 * 
//	 * TODO: UTIL: Problema no emparelhamento: inchaço de um nó específico, devido a ele sempre ser
//	 * emparelhado primeiro
//	 */
//	@SuppressWarnings("unused")
//	private void coarseGraphRandom(GraphWrapper grafo, GraphWrapper newGrafo) {
//
//		grafo.unlockAllNodes();
//
//		Random rand = new Random(System.currentTimeMillis());
//		ArrayList<RelationshipWrapper> list = new ArrayList<RelationshipWrapper>();
//
//		for (RelationshipWrapper aresta : grafo.getAllEdges()) {
//			list.add(aresta);
//		}
//		int size = list.size() - 1;
//
//		int i = 0;
//		RelationshipWrapper aresta = getRandomEdge(rand, list, size);
//		while (aresta != null) {
//			NodeWrapper v1 = aresta.getStartNode();
//			NodeWrapper v2 = aresta.getEndNode();
//			// se ambos vertices não foram usados
//			if (v1.getId() == v2.getId()) {
//				logger.error("3 - Verificar pq gera aresta pra ele mesmo. id: " + v1.getId());
//				// TODO: Verificar se realmente deve descartar a aresta para si mesmo.
//				continue;
//			}
//			mathcing.coarseVertex(newGrafo, v1, v2);
//
//			v1.lock();
//			v2.lock();
//			aresta = getRandomEdge(rand, list, size);
//			i++;
//		}
//		logger.warn("Qtd vertices: " + grafo.getSizeNodes() + " qtd Coarsed rand: " + i);
//		// mathcing.processRemainingVertices(grafo, newGrafo);
//		coarseGraphSequence(grafo, newGrafo);
//	}
//
//	private RelationshipWrapper getRandomEdge(Random rand, ArrayList<RelationshipWrapper> list, int size) {
//		int i = 0;
//		RelationshipWrapper aresta = null;
//		while (true) {
//			int index = Math.abs(rand.nextInt() % size);
//			aresta = list.get(index);
//			if (!aresta.getStartNode().isLocked() && !aresta.getEndNode().isLocked()) {
//				return aresta;
//			} else {
//				if (i == size) {
//					return null;
//				}
//				i++;
//			}
//		}
//	}

}
